#include "columnspec.h"
#include "exceptions.h"
#include "util.h"

#include <algorithm>
#include <cstdint>
#include <sstream>


namespace Database
{

using std::endl;
using std::string;
using std::ostringstream;
using std::stringstream;


ColumnSpec::ColumnSpec(const Type type, const Nullability nullable, const string defaultValue,
                       const bool defaultValueIsNull, const size_t maxByteLen)
	: type_(type), nullability_(nullable), defaultValue_(defaultValue),
	  defaultValueIsNull_(defaultValueIsNull)
{
	switch(type)
	{
		case INT_UNSIGNED:
			maxByteLen_ = sizeof(int32_t);
			break;

		case VARCHAR:
			maxByteLen_ = maxByteLen;
			break;
	}
}


ColumnSpec::~ColumnSpec()
{ }


const ColumnSpec::Type ColumnSpec::type() const
{
	return type_;
}


const bool ColumnSpec::isNullable() const
{
	return (nullability_ == NULLABLE);
}


const bool ColumnSpec::isVariableLength() const
{
	switch(type_)
	{
		case INT_UNSIGNED:
			return false;

		case VARCHAR:
			return true;
	}

	throw EIllegalValue();
}


const size_t ColumnSpec::maxByteLen() const
{
	return maxByteLen_;
}


const string ColumnSpec::asString() const
{
    // e.g.
    //  INT UNSIGNED NOT NULL DEFAULT 4
    //  VARCHAR(255) DEFAULT NULL

    ostringstream o;
	switch(type_)
	{
		case INT_UNSIGNED:
            o << "INT UNSIGNED ";
			break;

		case VARCHAR:
            o << "VARCHAR(" << maxByteLen_ << ")";
			break;
	}

    if(!isNullable())
        o << "NOT NULL ";
    o << "DEFAULT ";
    if(defaultValueIsNull_)
        o << "NULL";
    else
        o << "\"" << defaultValue_ << "\"";

    return o.str();
}


ColumnSpec ColumnSpec::fromString(string spec)
{
    using Util::ltrim;
    // e.g.
    //  INT UNSIGNED NOT NULL DEFAULT 4
    //  VARCHAR(255) DEFAULT NULL
    // in general:
    //  <type> [NOT NULL] [DEFAULT <default_val>]
    Type type;
    Nullability nullability;
    uint32_t maxByteLen = 0;
    string defaultValue;
    bool defaultValueIsNull;

    ltrim(spec);

    if(spec.find("INT UNSIGNED") == 0)
    {
        type = INT_UNSIGNED;
        spec.erase(0, string("INT UNSIGNED").length());
    }
    else if(spec.find("VARCHAR(") == 0)
    {
        type = VARCHAR;
        spec.erase(0, string("VARCHAR(").length());

        const string len_str = spec.substr(0, spec.find_first_not_of("0123456789"));
        if(!len_str.length())
            throw EColumnSpec();

        maxByteLen = ::atoi(len_str.c_str());

        spec.erase(0, len_str.length());

        if(!spec.length() || spec[0] != ')')
            throw EColumnSpec();
        spec.erase(0, 1);
    }
    else
    {
        throw EColumnSpec();
    }

    ltrim(spec);

    if(spec.find("NOT NULL") == 0)
    {
        nullability = NULLABLE;
        spec.erase(0, string("NOT NULL").length());
        ltrim(spec);
    }
    else
        nullability = NOT_NULLABLE;

    // FIXME: can't handle quoted default values
    // FIXME: can't detect default values inappropriate for column type
    //      (e.g. INT UNSIGNED NOT NULL DEFAULT 'hello')
    if(spec.find("DEFAULT ") == 0)
    {
        spec.erase(0, string("DEFAULT ").length());
        if(spec.find("NULL") == 0)
        {
            defaultValueIsNull = true;
            spec.erase(0, string("NULL").length());
        }
        else
        {
            defaultValue = spec;
            spec.erase(0, spec.npos);
        }
    }

    if(spec.length())
        throw EColumnSpec();

    return ColumnSpec(type, nullability, defaultValue, defaultValueIsNull, maxByteLen);
}


}	// namespace Database
